package hdikow;

import gui.AlgorithmParameter;
import gui.BioinfAlgorithm;
import gui.StringList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Vector;

/**
 *
 * Implementation of the Gotho Algorithm
 *
 * @author Heidi Dikow
 */
public class Gotho extends Needleman {

    private int openPenalty;
    private int extendPenalty;
    private MatrixEntry[][] E;
    private MatrixEntry[][] D;
    private MatrixEntry[][] L;
   

    /**
     * Constructor
     */
    public Gotho() {
        super();
        super.parameters.add(new AlgorithmParameter(
                "Gap extension penalty", "This is the value of the gap penalty"
                + " for extending gaps", Integer.class, new Integer(-1)));
    }

    @Override
    public String run(Vector<AlgorithmParameter> params) {

        //######### READ PARAMETERS ############################
        a = (String) params.elementAt(0).data;
        b = (String) params.elementAt(1).data;
        //checkbox if true means PAM250, false BLOSUM62
        boolean pam = (boolean) params.elementAt(2).data;
        matchMissmatch = (pam) ? "pam.txt" : "blosum.txt";
        openPenalty = (int) params.elementAt(3).data;
        scoringMatrix = Utility.buildEvaluationMatrix(matchMissmatch);
        characterIndex = Utility.buildCharacterIndex(matchMissmatch);
        allAlignments = (boolean) params.elementAt(4).data;
        extendPenalty = (int) params.elementAt(5).data;

        /*
         * check if the format of the input is correct if not do not execute
         * the program and return error message
         */

        if (!Utility.correctInput(a, b)) {
            return ("You inserted characters which are not in the single "
                    + "letter code format! \nIf you want to use this program "
                    + "please correct your input if not have a great day!");
        }

        /*
         *insert a random character in front of the sequences as the matrix 
         * needs to have the size of a.length()+1 and b.length()+1
         */
        a = "-" + a;
        b = "-" + b;
        constructMatrices();
        alignments = completeTraceback(matrix, a.length() - 1,
                b.length() - 1, "", "", "");
        for (String b : alignments) {
            System.out.println(b);
        }
        return alignments.get(0);
    }

    /**
     *
     * @param matrix
     * @param ia
     * @param ib
     * @param aliA
     * @param aliB
     * @param aliM
     * @return
     */
    public ArrayList<String> completeTraceback(MatrixEntry[][] matrix,
            int ia, int ib, String aliA, String aliB, String aliM) {

        ArrayList<String> giveBack = new ArrayList();
        if (ib == 0 || ia == 0) {
            while (ib != 0 && ib > 0) {
                aliA = "-" + aliA;
                aliB = b.substring(ib, ib + 1) + aliB;
                aliM = " " + aliM;
                ib--;
            }
            while (ia != 0 && ia > 0) {
                aliA = a.substring(ia, ia + 1) + aliA;
                aliB = "-" + aliB;
                aliM = " " + aliM;
                ia--;
            }
            giveBack.add("Seq1  " + aliA + "\n" + "      "
                    + aliM + "\n" + "Seq2  " + aliB);


        } else {

            if (matrix[ia][ib].isAbove()) {
                String t1 = "-" + aliA;
                String t2 = b.substring(ib, ib + 1) + aliB;
                String t3 = " " + aliM;
                ArrayList<String> innerList =
                        completeTraceback(matrix, ia, ib - 1, t1, t2, t3);
                for (String ali : innerList) {
                    giveBack.add(ali);
                }

            }
            if (matrix[ia][ib].isLeft()) {
                String t1 = a.substring(ia, ia + 1) + aliA;
                String t2 = "-" + aliB;
                String t3 = " " + aliM;
                ArrayList<String> innerList =
                        completeTraceback(matrix, ia - 1, ib, t1, t2, t3);
                for (String ali : innerList) {
                    giveBack.add(ali);
                }
            }
            if (matrix[ia][ib].isLeftAbove()) {
                String t1 = a.substring(ia, ia + 1) + aliA;
                String t2 = b.substring(ib, ib + 1) + aliB;
                String t3 = (matrix[ia][ib].isMatch()) ? "*" + aliM : ":" + aliM;
                ArrayList<String> innerList =
                        completeTraceback(matrix, ia - 1, ib - 1, t1, t2, t3);
                for (String ali : innerList) {
                    giveBack.add(ali);
                }
            }
        }
        return giveBack;

    }

    /**
     * Claculates the affine gap costs
     *
     * @param k number of already inserted gaps
     * @return the insertion costs
     */
    public int calculateCosts(int k) {
        return (openPenalty + (k * extendPenalty));
    }

    public void constructMatrices() {
        
        matrix = new MatrixEntry[a.length()][b.length()];
        D = new MatrixEntry[a.length()][b.length()];
        L = new MatrixEntry[a.length()][b.length()];
        E = new MatrixEntry[a.length()][b.length()];
        /*
         * Initialization
         */
        D[0][0] = new MatrixEntry(0);
        E[0][0] = new MatrixEntry(0);
        L[0][0] = new MatrixEntry(0);
        for (int i = 1; i < a.length(); i++) {
            int costs = calculateCosts(i);
            D[i][0] = new MatrixEntry(costs);
            E[i][0] = new MatrixEntry((int) Double.NEGATIVE_INFINITY);
            L[i][0] = new MatrixEntry(costs);
        }
        for (int j = 1; j < b.length(); j++) {
            int costs = calculateCosts(j);
            D[0][j] = new MatrixEntry(costs);
            E[0][j] = new MatrixEntry(costs);
            L[0][j] = new MatrixEntry((int) Double.NEGATIVE_INFINITY);
        }

        /*
         * Recursive construction
         */
        for (int i = 1; i < a.length(); i++) {
            for (int j = 1; j < b.length(); j++) {
                int score;
                MatrixEntry d = new MatrixEntry();
                MatrixEntry e = new MatrixEntry();
                MatrixEntry l = new MatrixEntry();
                MatrixEntry traceback = new MatrixEntry();
                //see for which characters the score is calculated
                String c1 = a.substring(i, i + 1);
                String c2 = b.substring(j, j + 1);
                //index of characters to look up the score                
                int indexOfA = characterIndex.get(c1) - 1;
                int indexOfB = characterIndex.get(c2) - 1;
                //look up score
                score = scoringMatrix[indexOfA][indexOfB];
                /*
                 * check if the two characters are equal, used for
                 * the match/missmatch output line
                 */
                if (c1.equals(c2)) {
                    d.setMatch(true);
                    e.setMatch(true);
                    traceback.setMatch(true);
                }
                int l1 = D[i - 1][j].getEntry() + extendPenalty + openPenalty;
                int l2;
                if (i > 2) {
                    l2 = L[i - 1][j].getEntry() + extendPenalty;
                } else {
                    l2 = (int) Double.NEGATIVE_INFINITY;
                }
                int e1 = D[i][j - 1].getEntry() + extendPenalty + openPenalty;
                int e2;
                if (j > 2) {
                    e2 = E[i][j - 1].getEntry() + extendPenalty;
                } else {
                    e2 = (int) Double.NEGATIVE_INFINITY;
                }

                int lEntry = Math.max(l1, l2);
                int eEntry = Math.max(e1, e2);
                e.setEntry(eEntry);
                l.setEntry(lEntry);
                E[i][j] = e;
                L[i][j] = l;
                int d1 = D[i - 1][j - 1].getEntry() + score;
                int d2 = E[i][j].getEntry();
                int d3 = L[i][j].getEntry();
                int dEntry = Utility.maxValue(d1, d2, d3);
                d.setEntry(dEntry);
                D[i][j] = d;
                if (d1 == dEntry) {
                    d.setLeftAbove(true);
                    traceback.setLeftAbove(true);
                }
                if (d2 == dEntry) {
                    traceback.setAbove(true);
                }
                if (d3 == dEntry) {
                    traceback.setLeft(true);
                }
                d.setEntry(dEntry);
                matrix[i][j] = traceback;
            }

        }


        /*
         * print 
         */
//        for (int i = 0; i < a.length(); i++) {
//            for (int j = 0; j < b.length(); j++) {
//                System.out.print(D[i][j].getEntry() + "#");
//            }
//            System.out.println();
//
//        }
//        System.out.println();
//        System.out.println();
//        for (int i = 0; i < a.length(); i++) {
//            for (int j = 0; j < b.length(); j++) {
//                System.out.print(L[i][j].getEntry() + "#");
//            }
//            System.out.println();
//
//        }
//        System.out.println();
//        System.out.println();
//        for (int i = 0; i < a.length(); i++) {
//            for (int j = 0; j < b.length(); j++) {
//                System.out.print(E[i][j].getEntry() + "#");
//            }
//            System.out.println();
//
//        }

    }

    @Override
    public String getName() {
        return "Gotho-Algorithm";
    }

    @Override
    public String getDescription() {
        return "Calculates the optimal alignment "
                + "of two sequences using affine gap costs";
    }

    @Override
    public Vector<AlgorithmParameter> getInputParameters() {
        return super.parameters;
    }
}
